﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;
using System.Concurrency;

namespace ReactiveCode.Observables.Expressions
{
    using Util;
    using Util.Expressions;
    using E = Expression;
    using OE = ObservableExpressionExtensions;

    abstract class ExpressionConverter: ExpressionVisitor<Expression>
    {
        protected abstract Type GenericResultTypeDefinition { get; }
        internal Expression _decorator;

        public virtual Expression Convert(Expression expression, Expression stateDecorator)
        {
            Contract.Requires(expression != null);
            Contract.Requires(stateDecorator != null && stateDecorator.Type == typeof(IStateDecorator));
            Contract.Ensures(Contract.Result<E>() != null && Contract.Result<E>().Type == GenericResultTypeDefinition.MakeGenericType(expression.Type));

            _decorator = stateDecorator;
            try
            {
                return Visit(expression);
            }
            finally
            {
                _decorator = null;
            }
        }
        public Expression<ObservableFactory<TResult>> Convert<TResult>(Expression expression)
        {
            Contract.Requires(expression != null);
            var decorator = E.Parameter(typeof(IStateDecorator), "d");
            var result = Convert(expression, decorator);
            Contract.Assert(result != null && result.Type == GenericResultTypeDefinition.MakeGenericType(expression.Type));

            var guardDelegate = E.IfThen(
                E.Equal(decorator, E.Constant(null, typeof(IStateDecorator))),
                E.Assign(decorator, E.Constant(NullStateDecorator.Instance))
            );

            return E.Lambda<ObservableFactory<TResult>>(E.Block(guardDelegate, result), decorator);
        }
        public ObservableFactory<TResult> ConvertEval<TResult>(Expression expression)
        {
            var lambda = Convert<TResult>(expression);
            return lambda.Compile();
        }

        Dictionary<E, E> _cache = new Dictionary<E, E>();
        protected override E Visit(E expr)
        {
            E result;
            if (!_cache.TryGetValue(expr, out result))
                _cache[expr] = result = base.Visit(expr);
            return result;
        }

        public static ObservableFactory<IObservable<T>> ToObservable<T>(Expression expression)
        {
            Contract.Requires(expression != null);
            return new ObservableConverter().ConvertEval<IObservable<T>>(expression);
        }
        public static ObservableFactory<ISubject<T>> ToSubject<T>(Expression expression)
        {
            Contract.Requires(expression != null);
            return new SubjectConverter().ConvertEval<ISubject<T>>(expression);
        }
        static Action<T> MakeSetter<T>(Expression member)
        {
            var newValue = E.Parameter(typeof(T));
            var lambda = E.Lambda<Action<T>>
            (
                E.Assign(member, newValue),
                newValue
            );
            return lambda.Compile();
        }
    }
}
